iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0
Modern Web

擁抱 .Net Core系列 第 14

[Day14] 選項模式(2) - Configuration - 5

  • 分享至 

  • xImage
  •  

具名Option

假設今天我有兩個Line Setting 都是相同環境做使用,且結構相同
appsettings.json

{
  "DogLineSetting": {
    "ChannelId": "dog_channel_id",
    "ChannelSecret": "dog_channel_secret",
    "Info": {
      "Name": "dog",
      "PictureUrl": "dog_picture_url"
    }
  },
  "CatLineSetting": {
    "ChannelId": "cat_channel_id",
    "ChannelSecret": "cat_channel_secret",
    "Info": {
      "Name": "cat",
      "PictureUrl": "cat_picture_url"
    }
  }
}

可以在設定Option的時候使用命名的方式為綁定的Option 命名
這種方式在dotnet core 其實很常見,諸如:HttpClient,Cors 等等。

調整一下ServiceCollection.Configure方法
舊(不使用命名)

var serviceProvider = new ServiceCollection()
    .AddOptions()
    .Configure<LineSetting>(configuration.GetSection("Line"))

使用命名,用 Configure\<T>(name:string, config:IConfiguration)的多載

var serviceProvider = new ServiceCollection()
    .AddOptions()
    .Configure<LineSetting>("Dog", configuration.GetSection("DogLineSetting"))
    .Configure<LineSetting>("Cat", cnfiguration.GetSection("CatLineSetting"))

注入,注意這邊使用的是IOptionsSnapshot,後面會提到為什麼不是IOptions

public class LineService
{
    private readonly LineSetting _lineSetting;

    public LineService(IOptionsSnapshot<LineSetting> lineSetting)
    {
        _lineSetting = lineSetting.Get("Dog");
    }

    public void PrintLineAppName()
    {
        Console.WriteLine(_lineSetting.Info.Name);
    }
}

OptionManager

我們偷偷的脫去AddOptions()的衣裳

OptionsServiceCollectionExtensions.cs

public static IServiceCollection AddOptions(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
    services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
    services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
    services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
    return services;
}

可以看見IOptions<>的實作為UnnamedOptionsManager<>
IOptionsSnapshot<>的實作為OptionsManager<>

我們先來看看UnnamedOptionsManager<>的實作

UnnamedOptionsManager

UnnamedOptionsManager.cs

public TOptions Value
{
    get
    {
        if (_value is TOptions value)
        {
            return value;
        }

        lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
        {
            return _value ??= _factory.Create(Options.DefaultName);
        }
    }
}

可以注意到當value 沒有值的時候,會從IOptionFactory 中取得一個 name 為"default"的 TOption的Instance
因此他是 unnamedOption

OptionsManager

OptionsManager.cs


public TOptions Value => Get(Options.DefaultName);
public virtual TOptions Get(string name)
{
    name = name ?? Options.DefaultName;

    if (!_cache.TryGetValue(name, out TOptions options))
    {
        // Store the options in our instance cache. Avoid closure on fast path by storing state into scoped locals.
        IOptionsFactory<TOptions> localFactory = _factory;
        string localName = name;
        options = _cache.GetOrAdd(name, () => localFactory.Create(localName));
    }

    return options;
}

當get Value 的屬性時,取得的是一個unname的Option。

而Get方法則會從當前快取(Scoped)中找值,如果沒有的話就靠factory 產一個
可以注意到他的參數是有name,意味著可以建立具名的Options


上一篇
[Day13] 選項模式 - Configuration - 4
下一篇
[Day15] Log - 1
系列文
擁抱 .Net Core30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言